# encoding=utf8
# aid 转 bid 代码取自：https://zhuanlan.zhihu.com/p/117358823
import json
import os
import re
import sys
import time
import traceback
from contextlib import closing
from math import ceil

import shutil
import requests
# 解决QPython的bug
# requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS = "TLS13-CHACHA20-POLY1305-SHA256:TLS13-AES-128-GCM-SHA256:TLS13-AES-256-GCM-SHA384:ECDHE:!COMPLEMENTOFDEFAULT"
from tqdm import tqdm
from Downloader import Downloader

from requests.packages.urllib3.exceptions import InsecureRequestWarning
 
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

debug = True
debug = False


class Bilibili:
    def __init__(self, url):
        self.url = url
        self.headers = {
            "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
            "accept-encoding": "gzip, deflate, br",
            "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
            "cache-control": "max-age=0",
            "referer": "https://www.bilibili.com/",
            "sec-fetch-dest": "document",
            "sec-fetch-mode": "navigate",
            "sec-fetch-site": "same-origin",
            "sec-fetch-user": "?1",
            "upgrade-insecure-requests": "1",
            "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        }
        self.Str = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'  # 准备的一串指定字符串
        self.s = [11, 10, 3, 8, 4, 6, 2, 9, 5, 7]
        self.xor = 177451812
        self.add = 100618342136696320
        if not os.path.exists('video'):
            os.mkdir('video')

    def getaid(self, bid):
        url = rf'https://www.bilibili.com/video/{bid}'
        a = requests.get(url, headers=self.headers)
        if bid[:2] == 'av':
            aid = bid[2:]
        else:
            aid = re.findall(r'"' + bid + r'":\{"aid":(.*?),', a.text)[0]
        title = re.findall(
            r'<h1 [^<>]*?title="(.*?)" class="video-title', a.text)[0]
        return aid, title
    
    def getbid(self, aid):
        if aid[:2] == 'av':
            aid = int(aid[2:])
            table = 'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'
            tr = {}
            for i in range(58):
                tr[table[i]] = i
            s = [11, 10, 3, 8, 4, 6]
            xor = 177451812
            add = 8728348608
            x = (aid ^ xor) + add
            r = list('BV1  4 1 7  ')
            for i in range(6):
                r[s[i]] = table[x // 58 ** i % 58]
            return ''.join(r)
        else:
            return aid

    def getcid(self, bid):
        url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + bid
        a = requests.get(url, headers=self.headers)
        a = a.json()
        with open('json.json', 'w', encoding='utf8') as f:
            f.write(str(a))
        ans = []
        if debug:
            print('bid', bid)
        data = a['data']
        cids = data
        for i in cids:
            cid = i['cid']
            title = i['part']
            ans.append({'bid': bid, 'cid': cid, 'title': title})
        return ans

    def getVideoList(self, bid, cid, quality):
        url_api = f'https://api.bilibili.com/x/player/playurl?cid={cid}&bvid={bid}&qn={quality}'
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
            'Host': 'api.bilibili.com'
        }
        a = requests.get(url_api, headers=headers)
        a = a.json()
        video_list = []
        for i in a['data']['durl']:
            video_list.append(i['url'])
        return video_list

    def validateTitle(self, title):
        rstr = r"[/\\\:\*\?\"\<\>\|]"  # '\ : * ? " < > |'
        new_title = re.sub(rstr, "_", title)  # 替换为下划线
        return new_title

    def download(self, url, filename, bid):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
            'Accept': '*/*',
            'Accept-Language': 'en-US,en;q=0.5',
            'Accept-Encoding': 'gzip, deflate, br',
            'Range': 'bytes=0-',
            'Referer': 'https://www.bilibili.com/video/' + bid,
            'Origin': 'https://www.bilibili.com',
            'Connection': 'keep-alive',
        }
        downloader = Downloader(url, filename, headers=headers, strict=False)
        downloader.main()

    def run(self):
        self.bid = self.getbid(self.bid)
        cid = self.getcid(self.bid)
        if debug:
            print('cid', cid)
        aid, title = self.getaid(self.bid)
        if debug:
            print('aid', aid)
        title = self.validateTitle(title)
        if len(cid) > 1:
            if not os.path.exists('./video/' + title):
                os.mkdir('./video/' + title)
            for ind, c in enumerate(cid):
                urls = self.getVideoList(self.bid, c['cid'], '80')

                Prefix = 0
                if len(cid) < 10:
                    Prefix = '%d-'
                elif len(cid) < 100:
                    Prefix = '%02d-'
                elif len(cid) < 1000:
                    Prefix = '%03d-'
                elif len(cid) < 10000:
                    Prefix = '%04d-'

                fileName = Prefix % (ind + 1) + self.validateTitle(c['title'])
                print(f'P{ind + 1}/{len(cid)} title: {fileName}')
                fileName = './video/' + title + '/' + fileName + '.flv'
                url = urls[0]
                self.download(url, fileName, self.bid)
        elif len(cid) == 1:
            urls = self.getVideoList(self.bid, cid[0]['cid'], '80')
            print('title', title)
            fileName = './video/' + title + '.flv'
            url = urls[0]
            self.download(url, fileName, self.bid)
        else:
            print('未解析到视频数据！')

    def main(self):
        try:
            self.bid = re.findall(r'video/([^?/]+)', self.url)[0]
        except:
            print('不支持的类型！')
            return
        for bugTimes in range(5):
            try:
                self.run()
                return
            except Exception as e:
                print(traceback.format_exc())
                print(f'运行出错，正在重试！bugTimes:{bugTimes}')


def top():
    with open('下载队列.txt', 'r') as f:
        a = f.readlines()
    if len(a) == 0:
        return None
    return a[0].strip('\n')


def pop():
    with open('下载队列.txt', 'r') as f:
        a = f.readlines()
    with open('下载队列.txt', 'w') as f:
        f.writelines(a[1:])


def main():
    print('开启服务...')
    while True:
        url = top()
        if url == None:
            time.sleep(1)
            continue
        a = Bilibili(url)
        a.main()
        pop()


if __name__ == '__main__':
    main()
